#pragma once

#define SMALL_STRING_SIZE 128

class CEX String:public Object
{
	// Large, dynamic buffer, in case the string is beyond the small string size
	Buffer* _bigBuf;

	// Small, static buffer, for small strings
	char _smallBuf[SMALL_STRING_SIZE];

	// Pointer to either _bigBuf or _smallBuf
	char* _buf;

	// Length of the string, excluding the terminal 0
	int _size;


	// Set the size of the string and allocate the buffer accordingly
	// size: Size to set _size to
	// allocsize: Number of bytes to actually allocate.  If -1, 'size' will be used
	// NOTE: This function also fixes the correct bigBuf/smallBuf relationship
	void setSize(int size,int allocSize=-1)
	{
		// Mark the specified size
		_size = size;

		// Fix the allocSize if needed
		if(allocSize==-1)
			allocSize = size;

		// Check if we should use a small or large buffer
		if(allocSize>=SMALL_STRING_SIZE)
		{
			// Allocate a large buffer
			_bigBuf = new Buffer(allocSize);

			// Point to the big buffer
			_buf = (char*)*_bigBuf;
		}
		else
		{
			// Point to the small buffer
			_buf = _smallBuf;
			_bigBuf = NULL;
		}
		
	}

public:
	
	virtual const char* __getName(){return "String";}
	

	// Constructor
	String()
	{
		// Create an empty, small string
		setSize(0);
	}

	// Constructor
	// o: Standard C string to copy
	String(const char* o)
	{
		// Copy the C string
		int size = strlen(o);

		setSize(size,size+1);
		memcpy(_buf,o,size+1);
	}

	// Constructor
	// Copy the provided fixed-size string and append a terminal \0
	// o: Pointer to the string to copy
	// size: Number of bytes in the string to copy, EXCLUDING any terminal \0
	String(const char* o,int size)
	{
		// Copy the provided data, providing enough room for the terminal 0
		setSize(size,size+1);
		memcpy(_buf,o,size);

		// Add a terminal \0
		_buf[size] = 0;
	}

	// Copy constructor
	// o: String to copy
	String(const String& o)
	{
		// Copy the string, including the terminal 0
		setSize(o._size,o._size+1);
		memcpy(_buf,o._buf,o._size+1);
	}

	// Concatenation constructor
	// Create a new string which is the result of two other concatenated strings
	// o1: First string (left)
	// o2: Second string (right)
	String(const String& o1,const String& o2)
	{
		// Allocate enough space for both strings
		setSize(o1._size+o2._size,o1._size+o2._size+1);

		// Copy the first string
		memcpy(_buf,o1._buf,o1._size);

		// Copy the second string
		memcpy(_buf+o1._size,o2._buf,o2._size+1);
	}

	// Concatenation constructor
	// Create a new string which is the result of two other concatenated strings
	// o1: First string (left)
	// o2: Second string (right)
	String(const String& o1,const char* o2)
	{
		int o2size = strlen(o2);
		
		// Allocate enough space for both strings
		setSize(o1._size+o2size,o1._size+o2size+1);

		// Copy the first string
		memcpy(_buf,o1._buf,o1._size);

		// Copy the second string
		memcpy(_buf+o1._size,o2,o2size+1);
	}

	// Constructor
	// Create a string based on an integer value
	// o: Integer to convert to string
	String(int o)
	{
		// Directly convert into the small buffer
		_size = sprintf(_smallBuf,"%i",o);
		_buf = _smallBuf;
		_bigBuf = NULL;
	}

	// Constructor
	// Create a string based on a float value
	// o: Integer to convert to string
	String(float o)
	{
		// Directly convert into the small buffer
		_size = sprintf(_smallBuf,"%f",o);
		_buf = _smallBuf;
		_bigBuf = NULL;
	}

	// Constructor
	// Create a string based on a double value
	// o: Integer to convert to string
	String(double o)
	{
		// Directly convert into the small buffer
		_size = sprintf(_smallBuf,"%f",o);
		_buf = _smallBuf;
		_bigBuf = NULL;
	}

	// Destructor
	~String()
	{
		// Clear the string
		clear();
	}

	// Clear the string, resetting it to an empty string
	void clear()
	{
		// Free the big buffer, if allocated
		if(_bigBuf)
			delete _bigBuf;

		// Reset to an empty string
		setSize(0,0);
		_buf[0] = 0;
	}

	// Affectation operator
	// o: String to copy
	String& operator=(const String& o)
	{
		// Clear the previous string
		clear();

		// Copy the new one
		setSize(o._size,o._size+1);
		memcpy(_buf,o._buf,o._size+1);

		return *this;
	}

	// Affectation operator
	// o: String to copy
	String& operator=(const char* o)
	{
		// Clear the previous string
		clear();

		// Copy the new one
		int size = strlen(o);
		setSize(size,size+1);
		memcpy(_buf,o,size+1);

		return *this;
	}

	// Addition operator
	// Returns a new string which is the result of the concatenation of this one and 'o'
	// o: String to concatenate to ourself
	String operator+(const String& o) const
	{
		// Create a new concatenated string
		return String(*this,o);
	}

	// Addition operator
	// Returns a new string which is the result of the concatenation of this one and 'o'
	// o: String to concatenate to ourself
	String operator+(const char* o) const
	{
		// Create a new concatenated string
		return String(*this,o);
	}

	// Concatenate a new string to this one
	// o: String to append to what we currently have
	String& operator+=(const String& o)
	{
		// Simply use the affectation and addition operators
		*this = *this+o;

		return *this;
	}

	// Concatenate a new string to this one
	// o: String to append to what we currently have
	String& operator+=(const char* o)
	{
		// Simply use the affectation and addition operators
		*this = *this+o;

		return *this;
	}

	// Multiplication operator
	// Return a new string, which is 'count' times ourself
	// count: Number of times to repeat ourselves
	String operator*(int count) const
	{
		// Return an empty string if count is less or equal to 0
		if(count<=0)
			return "";

		// Compute the required total size for the result string
		int size = _size*count;

		// Create the new string
		String s;

		// Set the size
		s.setSize(size,size+1);

		// Copy ourselves 'count' times
		char* ptr = s._buf;
		for(;count>0;count--)
		{
			// Copy one more instance
			memcpy(ptr,_buf,_size);
			ptr += _size;
		}

		// Set the terminal \0
		*ptr = 0;

		return s;
	}

	// Comparison operators
	// Perform the various comparisons
	bool operator<(const String& o) const		{	return strcmp(_buf,o._buf)	< 0;		}
	bool operator<(const char* o) const			{	return strcmp(_buf,o)		< 0;		}
	bool operator<=(const String& o) const		{	return strcmp(_buf,o._buf)	<= 0;		}
	bool operator<=(const char* o) const		{	return strcmp(_buf,o)		<= 0;		}
	bool operator>(const String& o) const		{	return strcmp(_buf,o._buf)	> 0;		}
	bool operator>(const char* o) const			{	return strcmp(_buf,o)		> 0;		}
	bool operator>=(const String& o) const		{	return strcmp(_buf,o._buf)	>= 0;		}
	bool operator>=(const char* o) const		{	return strcmp(_buf,o)		>= 0;		}
	bool operator==(const String& o) const		{	return strcmp(_buf,o._buf)	== 0;		}
	bool operator==(const char* o) const		{	return strcmp(_buf,o)		== 0;		}
	bool operator!=(const String& o) const		{	return strcmp(_buf,o._buf)	!= 0;		}
	bool operator!=(const char* o) const		{	return strcmp(_buf,o)		!= 0;		}

	// Array opertator
	// Return the character at the specified index
	// index: Index of the character to fetch
	char operator[](int index) const
	{
		ASSERT(index>=0 && index<=_size,"Index out of range")

		// Simply return the character
		return _buf[index];
	}

	// (const char*) cast
	// Returns the raw pointer to the string
	// NOTE: If the string is re-affected, through operator= or operator+=, the pointer
	//       previously returned by this function becomes invalid
	operator const char*() const
	{
		// Simply return the raw pointer
		return _buf;
	}

	// Return the length of the string, exclusing the terminal \0
	int getLen() const
	{
		return _size;
	}

	// Return a sub-section of the string, as a new string
	// start: First character index of the sub-string
	// len: Number of characters to copy, excluding the terminal \0
	String sub(int start=0,int len=0x07FFFFFF) const
	{
		// Clip start and len
		if(start<0)
		{
			len += start;
			start = 0;
		}

		if(start+len>_size)
			len = _size-start;

		// Return an empty string if the requested start/len combination is out of range
		if(len<=0 || start>=_size)
			return String();

		// Return the requested sub-section
		return String(_buf+start,len);
	}

	// Return the left-most part of the string
	// len: Number of characters to return
	String left(int len) const
	{
		return sub(0,len);
	}

	// Return the right-most part of the string
	// len: Number of characters to return
	String right(int len) const
	{
		return sub(_size-len,len);
	}

	// Return a new string which is ourself converted into uppercase
	String upper() const
	{
		// Copy the string
		String s(*this);

		// Convert each character to uppercase
		char* ptr = s._buf;
		while(*ptr)
		{
			*ptr = toupper(*ptr);
			ptr++;
		}

		// Return the new string
		return s;
	}
	
	// Return a new string which is ourself converted into uppercase
	String lower() const
	{
		// Copy the string
		String s(*this);

		// Convert each character to lowercase
		char* ptr = s._buf;
		while(*ptr)
		{
			*ptr = tolower(*ptr);
			ptr++;
		}

		// Return the new string
		return s;
	}

	// Search for the first occurence of a sub-string within ourself
	// str: Sub-string to search for
	// start: Index of the position to start the search
	// back: If true, the search will be done backwards
	// Returns: The index of the first found occurence of 'str', or -1 if not found
	int pos(const String& str,int start=0,bool back=false) const
	{
		// Remember the sub-string length
		int len = str._size;

		if(len==0)
			return -1;

		if(back)
		{
			// Search backwards
			if((start+len)>_size)
				start = _size-len;
			
			for(;start>=0;start--)
				if(strncmp(_buf+start,str._buf,len)==0)
					return start;
		}
		else
		{
			// Search forward
			if(start<0)
				start = 0;
			
			for(;start<=(_size-len);start++)
				if(strncmp(_buf+start,str._buf,len)==0)
					return start;
		}

		// Not found
		return -1;
	}

	// Create a new string based on a format
	// fmt: printf-like format
	// ...: Appropriate format elements
	static String format(const char* fmt,...)
	{
		va_list vl;
		String s;

		// Compute the resulting string size
		va_start(vl,fmt);
		int size = vsnprintf(NULL,0,fmt,vl);

		// Allocate the block
		s.setSize(size,size+1);

		// Build the string
		va_start(vl,fmt);
		vsprintf(s._buf,fmt,vl);

		// Mark the size
		s._size = size;

		return s;
	}

	// Split (explode) a string
	// str: Character used to split elements
	// includeEmpty: If set to true, two instances of 'str' following each other
	//               will cause an empty string to be added.  If true, and the string
	//               starts or ends with 'str', and empty string will also be including
	//               at the beginning or the end.
	// Returns: An array of String* containing all the elements of the split
	Array<String*> split(const String& str,bool includeEmpty=true) const
	{
		Array<String*> arr;
		int start = 0;

		while(start<_size)
		{
			// Get the position of the next occurence of 'str'
			int p = pos(str,start);

			// Check if there is no more
			if(p==-1)
				break;

			// Skip if two instances of 'str' follow each other and we don't want empty strings
			if(p==start && !includeEmpty)
			{
				start++;
				continue;
			}

			// Add a new section to the array
			arr.add(new String(_buf+start,p-start));
			start = p+1;
		}

		// Add the final section, if appropriate
		if(includeEmpty || start<_size)
			arr.add(new String(_buf+start,_size-start));
		
		// Return the array
		return arr;
	}

};

